package com.lingtianyu.tech.erbaoshen.core.consumer.service;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.lingtianyu.tech.erbaoshen.core.config.ErBaoShenRpcApplicationContextAware;
import com.lingtianyu.tech.erbaoshen.core.consumer.openfeign.ErBaoShenOpenFeignContext;
import com.lingtianyu.tech.erbaoshen.core.consumer.openfeign.OpenFeignErBaoShenClient;
import com.lingtianyu.tech.erbaoshen.core.context.ErBaoShenRpcContext;
import com.lingtianyu.tech.erbaoshen.core.provider.bean.ErBaoShenRpcServiceBeans;
import com.lingtianyu.tech.erbaoshen.core.provider.bean.ServiceBean;
import com.lingtianyu.tech.erbaoshen.core.provider.model.ErBaoShenRpcReq;
import com.lingtianyu.tech.erbaoshen.core.provider.model.ErBaoShenRpcResp;
import com.lingtianyu.tech.erbaoshen.core.util.ErBaoShenFinals;
import com.lingtianyu.tech.erbaoshen.core.util.KeyUtil;
import com.lingtianyu.tech.erbaoshen.core.util.SerializeUtil;
import io.vavr.Tuple;
import io.vavr.Tuple2;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.core.env.Environment;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.*;

import static com.lingtianyu.tech.erbaoshen.core.util.ErBaoShenFinals.*;

@Slf4j
public class GenericService {
    private final RestTemplate lbRestTemplate;
    private final ErBaoShenRpcServiceBeans erBaoShenRpcServiceBeans;
    private final DiscoveryClient discoveryClient;
    private Random random = new Random();
    private final SerializeUtil serializeUtil;
    private final Environment env;

    private static final ObjectMapper objectMapper = new ObjectMapper();

    public GenericService(RestTemplate lbRestTemplate, ErBaoShenRpcServiceBeans erBaoShenRpcServiceBeans, Environment env, DiscoveryClient discoveryClient,SerializeUtil serializeUtil) {
        this.lbRestTemplate = lbRestTemplate;
        this.erBaoShenRpcServiceBeans = erBaoShenRpcServiceBeans;
        this.env = env;
        this.discoveryClient = discoveryClient;
        this.serializeUtil = serializeUtil;
    }

    public <T> Tuple2<Object, Map<String, List<String>>> runMethod(String className, String group, String version, String methodName, List<String> paramTypes, List<Object> params, Type resultType) {
        try {
            String key = KeyUtil.getConsumerKey(className, group, version);
            ServiceBean serviceBean = erBaoShenRpcServiceBeans.getServiceBeanMap().get(key);
            if (serviceBean != null) {
                Object ref = serviceBean.getRef();
                if (paramTypes == null || paramTypes.isEmpty()) {
                    Method method = serviceBean.getInterfaceClass().getMethod(methodName);
                    Object invoke = method.invoke(ref);
                    return Tuple.of(invoke, new HashMap<>());
                } else {
                    Class<?>[] classes = new Class<?>[paramTypes.size()];
                    for (int i = 0; i < paramTypes.size(); i++) {
                        String paramType = paramTypes.get(i);
                        if ("boolean".equals(paramType)) {
                            classes[i] = boolean.class;
                            continue;
                        } else if ("char".equals(paramType)) {
                            classes[i] = char.class;
                            continue;
                        } else if ("byte".equals(paramType)) {
                            classes[i] = byte.class;
                            continue;
                        } else if ("short".equals(paramType)) {
                            classes[i] = short.class;
                            continue;
                        } else if ("int".equals(paramType)) {
                            classes[i] = int.class;
                            continue;
                        } else if ("long".equals(paramType)) {
                            classes[i] = long.class;
                            continue;
                        } else if ("float".equals(paramType)) {
                            classes[i] = float.class;
                            continue;
                        } else if ("double".equals(paramType)) {
                            classes[i] = double.class;
                            continue;
                        }
                        Class<?> aClass = Class.forName(paramType);
                        classes[i] = aClass;
                    }

                    Method method = serviceBean.getInterfaceClass().getMethod(methodName, classes);
                    Object invoke = method.invoke(ref, params.toArray());
                    return Tuple.of(invoke, new HashMap<>());
                }
            }


//            List<Instance> allInstances = namingService.getAllInstances(key, nacosDiscoveryProperties.getGroup(), Lists.newArrayList(nacosDiscoveryProperties.getClusterName()));
            List<ServiceInstance> allInstances = discoveryClient.getInstances(key);
            if (allInstances.size() == 0) {
                throw new RuntimeException("service: " + key + " not found ");
            }
            //暂时使用随机算法，后续考虑支持灰度
            int i = random.nextInt(allInstances.size());
            ServiceInstance instance = allInstances.get(i);
            String fromService = instance.getMetadata().get(ErBaoShenFinals.FROM_SERVICE);
            if (fromService == null || fromService.isEmpty()) {
                throw new NullPointerException("fromService not found");
            }
            ErBaoShenRpcReq erBaoShenRpcReq = new ErBaoShenRpcReq();
            erBaoShenRpcReq.setGroup(group);
            erBaoShenRpcReq.setVersion(version);
            erBaoShenRpcReq.setParamTypes(paramTypes);

            if(env.getProperty(SERIALIZE_TYPE_KEY) != null && !env.getProperty(SERIALIZE_TYPE_KEY).isEmpty()) {
                erBaoShenRpcReq.setSerializeType(env.getProperty(SERIALIZE_TYPE_KEY));
            }
            //对参数重新序列化
            List<Object> newParams = new ArrayList<>(params.size());
            for (Object param : params) {
                Object serializeParam = serializeUtil.toSerializeParams(param, erBaoShenRpcReq.getSerializeType());
                newParams.add(serializeParam);
            }
            erBaoShenRpcReq.setParams(newParams);
            Map<String, Object> context = ErBaoShenRpcContext.getAttachments();
            erBaoShenRpcReq.setContext(context);
            String clientType = env.getProperty(CLIENT_TYPE_KEY);
            if ("openfeign".equalsIgnoreCase(clientType)) {
                Object openFeignErBaoShenClient = ErBaoShenRpcApplicationContextAware.getApplicationContext().getBean(OpenFeignErBaoShenClient.class);
                if (openFeignErBaoShenClient == null) {
                    throw new RuntimeException("openfeign error ,no bean com.lingtianyu.tech.erbaoshen.core.consumer.openfeign.OpenFeignErBaoShenClient ");
                }
                ErBaoShenOpenFeignContext.setServerName(fromService);
                OpenFeignErBaoShenClient ofhc = (OpenFeignErBaoShenClient) openFeignErBaoShenClient;
                ResponseEntity<String> responseEntity = ofhc.runRpcService(className, methodName, objectMapper.writeValueAsString(erBaoShenRpcReq));
                if (responseEntity.getStatusCode().is2xxSuccessful()) {
                    return Tuple.of(serializeUtil.unSerializeParams(responseEntity.getBody(), resultType, getSerializeTypeHeaderFromResp(responseEntity)), responseEntity.getHeaders());
                } else if (HttpStatus.BAD_REQUEST.equals(responseEntity.getStatusCode())) {
                    ErBaoShenRpcResp erBaoShenRpcResp = objectMapper.readValue(responseEntity.getBody(), ErBaoShenRpcResp.class);
                    log.error(" run error erBaoShenRpcResp errorCode: " + erBaoShenRpcResp.getErrorCode() + " error message:" + erBaoShenRpcResp.getErrorMessage());
                    throw new RuntimeException(erBaoShenRpcResp.getErrorMessage());
                } else {
                    throw new RuntimeException("error http: " + responseEntity.getStatusCodeValue() + " headers " + objectMapper.writeValueAsString(responseEntity.getHeaders()) + " body " + responseEntity.getBody());
                }
            } else {
                ResponseEntity<String> responseEntity = lbRestTemplate.postForEntity(getUrl(fromService, className, methodName), objectMapper.writeValueAsString(erBaoShenRpcReq), String.class);
                if (responseEntity.getStatusCode().is2xxSuccessful()) {
                    return Tuple.of(serializeUtil.unSerializeParams(responseEntity.getBody(), resultType, getSerializeTypeHeaderFromResp(responseEntity)), responseEntity.getHeaders());
                } else if (HttpStatus.BAD_REQUEST.equals(responseEntity.getStatusCode())) {
                    ErBaoShenRpcResp erBaoShenRpcResp = objectMapper.readValue(responseEntity.getBody(), ErBaoShenRpcResp.class);
                    log.error(" run error erBaoShenRpcResp errorCode: " + erBaoShenRpcResp.getErrorCode() + " error message:" + erBaoShenRpcResp.getErrorMessage());
                    throw new RuntimeException(erBaoShenRpcResp.getErrorMessage());
                } else {
                    throw new RuntimeException("error http: " + responseEntity.getStatusCodeValue() + " headers " + objectMapper.writeValueAsString(responseEntity.getHeaders()) + " body " + responseEntity.getBody());
                }
            }
        } catch (NoSuchMethodException e) {
            log.error(e.getMessage(), e);
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            log.error(e.getMessage(), e);
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            log.error(e.getMessage(), e);
            throw new RuntimeException(e);
        } catch (ClassNotFoundException e) {
            log.error(e.getMessage(), e);
            throw new RuntimeException(e);
        } catch (JsonMappingException e) {
            log.error(e.getMessage(), e);
            throw new RuntimeException(e);
        } catch (JsonProcessingException e) {
            log.error(e.getMessage(), e);
            throw new RuntimeException(e);
        } finally {
            ErBaoShenOpenFeignContext.clear();
        }
    }

    private String getUrl(String fromService, String className, String methodName) {
        return "http://" + fromService + "/erbaoshen/rpc/service/" + className + "/" + methodName;
    }

    private String getSerializeTypeHeaderFromResp(ResponseEntity responseEntity) {
        List<String> strings = responseEntity.getHeaders().get(SERIALIZE_TYPE_HEADER);
        if(strings == null || strings.isEmpty()) {
            return null;
        }
        return strings.get(0);
    }
}
